/* eslint-disable react-hooks/rules-of-hooks */
import { useEffect, useState } from 'react'

/* eslint-disable @typescript-eslint/ban-ts-comment */
type ValueUpdater<T> = T | ((v: T) => T)

export class TinyStore<T = object> {
  protected state: T

  constructor(defaultData: T) {
    this.state = { ...defaultData }
  }

  protected setValue<K extends keyof T>(key: K, value: T[K]) {
    this.state[key] = value
    this.state = this.computedState(this.state)
    this.notify()
  }

  protected setState<K extends keyof T>(s: {
    [key in K]: ValueUpdater<T[key]>
  }) {
    this.state = { ...this.state }
    Object.entries(s).forEach(([key, value]) => {
      if (typeof value === 'function') {
        // @ts-ignore
        this.state[key] = value(this.state[key])
      } else {
        // @ts-ignore
        this.state[key] = value
      }
    })
    this.state = this.computedState(this.state)
    this.notify()
  }

  getState() {
    return this.state
  }

  getValue<K extends keyof T>(key: K) {
    return this.state[key]
  }

  protected computedState(state: T) {
    return state
  }

  private handlers: Set<(formData: T) => void> = new Set()
  subscript(fn: (formData: T) => void) {
    this.handlers.add(fn)
    return () => void this.handlers.delete(fn)
  }

  notify() {
    this.handlers.forEach((fn) => fn(this.state))
  }

  use<D>(getter: (state: T) => D): D {
    const [state, setState] = useState(() => getter(this.state))
    // eslint-disable-next-line react-hooks/exhaustive-deps
    useEffect(() => this.subscript((formData) => setState(getter(formData))), [this, getter])
    return state
  }
}
